package com.sl.biz.core.openapi;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sl.biz.modular.product.dao.entity.ProductDO;
import com.sl.biz.modular.product.service.ProductBizService;
import com.sl.biz.modular.restapi.controller.request.ApiConfigQueryByIdsRequest;
import com.sl.biz.modular.restapi.controller.response.ApiVersionConfigQueryByApiIdVO;
import com.sl.biz.modular.restapi.dao.entity.ApiConfigDO;
import com.sl.biz.modular.restapi.dao.entity.ApiEntityConfigDO;
import com.sl.biz.modular.restapi.dao.entity.ApiVersionConfigDO;
import com.sl.biz.modular.restapi.dao.enums.ApiStatusCodeEnum;
import com.sl.biz.modular.restapi.service.ApiConfigBizService;
import com.sl.common.pojo.CommonEntity;
import com.sl.core.domain.dto.JsonFieldDefine;
import com.sl.core.utils.JsonUtil;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.servers.ServerVariables;
import jakarta.servlet.http.HttpServletRequest;
import org.springdoc.core.customizers.SpringDocCustomizers;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.providers.SpringDocProviders;
import org.springdoc.core.service.AbstractRequestService;
import org.springdoc.core.service.GenericResponseService;
import org.springdoc.core.service.OpenAPIService;
import org.springdoc.core.service.OperationService;
import org.springdoc.webmvc.api.OpenApiWebMvcResource;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Value;

import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import static org.springdoc.core.utils.Constants.API_DOCS_URL;
import static org.springdoc.core.utils.Constants.POST_METHOD;


public class CustomerOpenApiWebMvcResource extends OpenApiWebMvcResource {
    private final ApiConfigBizService apiConfigBizService;
    private final ProductBizService productBizService;


    public CustomerOpenApiWebMvcResource(String groupName, ObjectFactory<OpenAPIService> openAPIBuilderObjectFactory, AbstractRequestService requestBuilder, GenericResponseService responseBuilder, OperationService operationParser, SpringDocConfigProperties springDocConfigProperties, SpringDocProviders springDocProviders, SpringDocCustomizers springDocCustomizers,
                                         ApiConfigBizService apiConfigBizService, ProductBizService productBizService) {
        super(groupName, openAPIBuilderObjectFactory, requestBuilder, responseBuilder, operationParser, springDocConfigProperties, springDocProviders, springDocCustomizers);
        this.apiConfigBizService = apiConfigBizService;
        this.productBizService = productBizService;
    }

    @Override
    public byte[] openapiJson(HttpServletRequest request, @Value(API_DOCS_URL) String apiDocsUrl, Locale locale)
            throws JsonProcessingException {

        IgnoreStrategy ignoreStrategy = IgnoreStrategy.builder().tenantLine(true).build();
        InterceptorIgnoreHelper.handle(ignoreStrategy);
        ProductDO productDO = productBizService.queryByCode(this.groupName);

        ApiConfigDO apiConfigDO = new ApiConfigDO();
        apiConfigDO.setProductId(productDO.getId());
        apiConfigDO.setApiStatusCode(ApiStatusCodeEnum.ONLINE.getCode());

        List<ApiConfigDO> list = apiConfigBizService.list(apiConfigDO);

        ApiConfigQueryByIdsRequest apiConfigQueryByIdRequest = new ApiConfigQueryByIdsRequest();
        apiConfigQueryByIdRequest.setIds(list.stream().map(CommonEntity::getId).distinct().collect(Collectors.toList()));
        List<ApiVersionConfigQueryByApiIdVO> apiVersionConfigQueryByApiIdVOS = apiConfigBizService.queryVersionByApiIds(apiConfigQueryByIdRequest);
        InterceptorIgnoreHelper.clearIgnoreStrategy();

        OpenAPI openAPI = new OpenAPI(springDocConfigProperties.getSpecVersion());
        openAPI.setComponents(buildComponents(productDO, list, apiVersionConfigQueryByApiIdVOS));

        InterceptorIgnoreHelper.clearIgnoreStrategy();
        openAPI.setInfo(buildInfos(productDO));
        openAPI.setServers(buildServers());
        openAPI.setPaths(buildPaths(productDO, list, apiVersionConfigQueryByApiIdVOS));
        openAPI.setExtensions(buildExtensions());
        return writeJsonValue(openAPI);
    }

    protected Paths buildPaths(ProductDO productDO, List<ApiConfigDO> list, List<ApiVersionConfigQueryByApiIdVO> apiVersionConfigQueryByApiIdVOS) {
        Paths paths = new Paths();
        Map<String, Object> extensions = Maps.newHashMap();
        paths.setExtensions(extensions);


        if (CollUtil.isEmpty(list)) {
            return paths;
        }
        String camelContextPath = SpringUtil.getProperty("camel.servlet.mapping.context-path");

        camelContextPath = StrUtil.replace(camelContextPath, "/*", "");

        Map<String, List<ApiVersionConfigQueryByApiIdVO>> apiIdMap = apiVersionConfigQueryByApiIdVOS.stream().collect(Collectors.groupingBy(ApiVersionConfigDO::getApiId));
        for (ApiConfigDO configDO : list) {
            List<ApiVersionConfigQueryByApiIdVO> apiIdVOList = apiIdMap.get(configDO.getId());
            ApiVersionConfigQueryByApiIdVO apiVersionConfigQueryByApiIdVO = null;
            //只有一个
            if (CollUtil.size(apiIdVOList) == 1) {
                apiVersionConfigQueryByApiIdVO = CollUtil.get(apiIdVOList, 0);
            } else {
                apiVersionConfigQueryByApiIdVO = apiIdVOList.stream().filter(v -> BooleanUtil.isTrue(v.getLatestFlag())).findFirst().orElse(null);

            }
            String apiCode = configDO.getApiCode();

            if (ObjectUtil.isNull(apiVersionConfigQueryByApiIdVO)) {
                continue;
            }
            String contextPath = configDO.getContextPath();
            String path = configDO.getPath();
            if (StrUtil.isBlank(path)) {
                continue;
            }
            String doMethods = configDO.getMethods();
            if (StrUtil.isBlank(doMethods)) {
                doMethods = POST_METHOD;
            }
            List<String> methodList = Lists.newArrayList();
            if (JSONUtil.isTypeJSON(doMethods)) {
                methodList = JSONUtil.toList(doMethods, String.class);
            } else {
                methodList.add(StrUtil.replace(doMethods, "\"", ""));
            }


            String apiName = configDO.getApiName();
            String description = configDO.getDescription();

            Map<String, Object> defineMap = Maps.newHashMap();
            defineMap.put("tags", Lists.newArrayList(apiName));
            defineMap.put("summary", description);
            defineMap.put("operationId", apiCode + "_1");
            defineMap.put("x-author", "sss");
            defineMap.put("x-order", "12222");
            String s = StrUtil.upperFirst(apiCode);

            //请求体构造
            Map<String, Object> requestBody = Maps.newHashMap();
            requestBody.put("required", true);
            Map<String, Object> content = Maps.newHashMap();

            Map<String, Object> contentType = Maps.newHashMap();

            Map<String, Object> schema = Maps.newHashMap();

            schema.put("$ref", "#/components/schemas/" + s + "Param");


            contentType.put("schema", schema);
            content.put("application/json", contentType);
            requestBody.put("content", content);


            defineMap.put("requestBody", requestBody);


            Map<String, Object> responses = Maps.newHashMap();
            Map<String, Object> two = Maps.newHashMap();
            two.put("description", "OK");

            Map<String, Object> ct = Maps.newHashMap();

            Map<String, Object> ctt = Maps.newHashMap();

            Map<String, Object> cttt = Maps.newHashMap();
            cttt.put("$ref", "#/components/schemas/" + s + "Response");
            ctt.put("schema", cttt);
            ct.put("*/*", ctt);
            two.put("content", ct);
            responses.put("200", two);
            defineMap.put("responses", responses);


            //每个接口都需要token
            List<Map<String, Object>> headers = Lists.newArrayList();
            Map<String, Object> headerMap = Maps.newHashMap();
            headerMap.put("in", "header");
            headerMap.put("name", "token");
            headerMap.put("required", true);
            Map<String, Object> headerSchemaMap = Maps.newHashMap();
            headerSchemaMap.put("type", "string");
            headerSchemaMap.put("description", "登陆token");
            headerMap.put("schema", headerSchemaMap);
            headers.add(headerMap);
            defineMap.put("parameters", headers);


            Map<String, Object> methods = Maps.newHashMap();
            //增加函数
            for (String method : methodList) {
                methods.put(method.toLowerCase(), defineMap);
            }

            extensions.put(StrUtil.concat(true, camelContextPath, contextPath, path), methods);


        }

        return paths;
    }

    protected Map<String, Object> buildExtensions() {
        Map<String, Object> map = new HashMap<>();

        Map<String, Object> openApi = new HashMap<>();
        openApi.put("x-markdownFiles", Lists.newArrayList());
        Map<String, Object> setting = Maps.newHashMap();
        setting.put("customCode", 200);
        setting.put("enableAfterScript", true);
        setting.put("enableDebug", true);
        setting.put("enableDocumentManage", true);
        setting.put("enableDynamicParameter", true);
        setting.put("enableFilterMultipartApiMethodType", "POST");
        setting.put("enableFilterMultipartApis", true);
        setting.put("enableFooter", false);
        setting.put("enableFooterCustom", true);
        setting.put("enableGroup", true);
        setting.put("enableHomeCustom", false);
        setting.put("enableHost", true);
        setting.put("enableHostText", "");
        setting.put("enableOpenApi", true);
        setting.put("enableReloadCacheParameter", false);
        setting.put("enableRequestCache", false);
        setting.put("enableResponseCode", true);
        setting.put("enableSearch", true);
        setting.put("enableSwaggerModels", false);
        setting.put("enableVersion", true);
        setting.put("footerCustomContent", "Apache License 2.0 | Copyright 2024-[SQUARE LAKE](https://116.198.243.213)");
        setting.put("language", "zh-CN");
        setting.put("swaggerModelName", "Swagger Models");

        openApi.put("x-setting", setting);
        map.put("x-openapi", openApi);
        return map;
    }

    protected List<Server> buildServers() {

        List<Server> list = Lists.newArrayList();
        Server server = new Server();
        server.setUrl("https://116.198.243.213");
        server.setDescription("魔盒");
        ServerVariables serverVariables = new ServerVariables();
        serverVariables.setExtensions(Maps.newHashMap());
        server.setVariables(serverVariables);
//        server.setExtensions();
        list.add(server);
        return list;
    }

    protected Info buildInfos(ProductDO productDO) {
        Info info = new Info();
        info.setTitle(productDO.getProductName());
        info.setDescription(productDO.getDescription());
//        info.setTermsOfService();
        Contact contact = new Contact();
        contact.setName("胡逸");
        contact.setUrl("https://116.198.243.213");
        contact.setEmail("1148577609@qq.com");
//        contact.setExtensions("");

        info.setContact(contact);
        License license = new License();
        license.setName("license");
        license.setUrl("https://116.198.243.213");
        license.setIdentifier(IdUtil.fastSimpleUUID());
//        license.setExtensions();
        info.setLicense(license);
        info.setVersion("v0");
        info.setSummary("sumary");
        return info;
    }


    protected Components buildComponents(ProductDO productDO, List<ApiConfigDO> list, List<ApiVersionConfigQueryByApiIdVO> apiVersionConfigQueryByApiIdVOS) {

        Components components = new Components();

        Map<String, Schema> schemas = Maps.newHashMap();
        components.setSchemas(schemas);

        if (CollUtil.isEmpty(list)) {
            return components;
        }
        Map<String, List<ApiVersionConfigQueryByApiIdVO>> apiIdMap = apiVersionConfigQueryByApiIdVOS.stream().collect(Collectors.groupingBy(ApiVersionConfigDO::getApiId));

        for (ApiConfigDO configDO : list) {
            List<ApiVersionConfigQueryByApiIdVO> apiIdVOList = apiIdMap.get(configDO.getId());
            ApiVersionConfigQueryByApiIdVO apiVersionConfigQueryByApiIdVO = null;
            //只有一个
            if (CollUtil.size(apiIdVOList) == 1) {
                apiVersionConfigQueryByApiIdVO = CollUtil.get(apiIdVOList, 0);
            } else {
                apiVersionConfigQueryByApiIdVO = apiIdVOList.stream().filter(v -> BooleanUtil.isTrue(v.getLatestFlag())).findFirst().orElse(null);

            }
            String apiCode = configDO.getApiCode();

            if (ObjectUtil.isNull(apiVersionConfigQueryByApiIdVO)) {
                continue;
            }

            ApiEntityConfigDO entity = apiVersionConfigQueryByApiIdVO.getEntity();
            String inputParam = entity.getInputParam();

            Boolean inputParamArrayFlag = entity.getInputParamArrayFlag();
            String inputBasicFieldType = entity.getInputBasicFieldType();
            Boolean inputParamBasicTypeFlag = entity.getInputParamBasicTypeFlag();


            if (BooleanUtil.isTrue(inputParamBasicTypeFlag)) {
                Schema schema = new Schema();
                schema.setType(inputBasicFieldType.toLowerCase());
                schemas.put("args", schema);
            } else {

                List<JsonFieldDefine> inputFieldDefine = JsonUtil.toList(inputParam, JsonFieldDefine.class);
                String s = StrUtil.upperFirst(apiCode);
                Schema schema = buildSchema(s, inputFieldDefine, schemas);
                schemas.put(s + "Param", schema);
            }

            String resultParam = entity.getResultParam();
            Boolean resultParamArrayFlag = entity.getResultParamArrayFlag();
            String resultBasicFieldType = entity.getResultBasicFieldType();
            Boolean resultParamBasicTypeFlag = entity.getResultParamBasicTypeFlag();

            if (BooleanUtil.isTrue(resultParamBasicTypeFlag)) {
                Schema schema = new Schema();
                schema.setType(resultBasicFieldType.toLowerCase());
                schemas.put("args", schema);
            } else {

                List<JsonFieldDefine> inputFieldDefine = JsonUtil.toList(resultParam, JsonFieldDefine.class);
                String s = StrUtil.upperFirst(apiCode);
                Schema schema = buildSchema(s, inputFieldDefine, schemas);
                schemas.put(s + "Response", schema);
            }
        }
        return components;
    }

    protected Schema buildSchema(String apiCode, List<JsonFieldDefine> inputFieldDefine, Map<String, Schema> schemas) {

        if (CollUtil.isEmpty(inputFieldDefine)) {
            return null;
        }

        Schema schema = new Schema();
        schema.setType("object");

        Map<String, Schema> fieldTypeSchema = Maps.newHashMap();
        List<String> requiredList = Lists.newArrayList();
        schema.setProperties(fieldTypeSchema);
        schema.setRequired(requiredList);

        for (JsonFieldDefine jsonFieldDefine : inputFieldDefine) {

            Schema schema1 = new Schema();
            String fieldType = jsonFieldDefine.getFieldType();
            if (StrUtil.isBlank(fieldType)) {
                fieldType = "string";
            }
            schema1.setType(fieldType.toLowerCase());
            schema1.setDescription(jsonFieldDefine.getFieldDesc());
            String fieldName = jsonFieldDefine.getFieldName();
            String fieldCode = jsonFieldDefine.getFieldCode();
            List<JsonFieldDefine> children = jsonFieldDefine.getChildren();

            Schema childrenSchema = buildSchema(apiCode, children, schemas);
            if (ObjectUtil.isNotNull(childrenSchema)) {
                String name = apiCode + StrUtil.upperFirst(fieldCode) + "Param";
                schemas.put(name, childrenSchema);
//                fieldTypeSchema.put(jsonFieldDefine.getFieldCode(), childrenSchema);
                schema1.set$ref("#/components/schemas/" + name);
            }
            fieldTypeSchema.put(jsonFieldDefine.getFieldCode(), schema1);


        }
        return schema;
    }
}
